package edu.towson.cosc.cosc455.kkarolenko.markdowncompiler;

import java.util.Stack;

import edu.towson.cosc.cosc455.interfaces.SyntaxAnalyzer;

// Analyze tokens and create a parse tree
public class MSyntaxAnalyzer implements SyntaxAnalyzer {

	private MLexicalAnalyzer lex = Compiler.lex;
	private Stack<Node<Token>> currentParent = new Stack<Node<Token>>();
	
	// Start the analyzing with the document rule
	@Override
	public void document() throws CompilerException {

		// Place strings in config file
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("SD")) {
			String message = "A #START DOCUMENT annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.";
			throw new CompilerException(message);
		}
		
		Compiler.tree = new Node<Token>(Compiler.getLastToken(), null);
		currentParent.push(Compiler.tree);
		
		lex.getNextToken();
		
		variableDefine();
		head();
		body();

		// Place strings in config file
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("ED")) {
			String message = "An #END DOCUMENT annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.";
			throw new CompilerException(message);
		}
		
		new Node<Token>(Compiler.getLastToken(), currentParent.peek());
		currentParent.pop();
		
		// There shouldn't be any characters after this
		if (lex.getCharacter()) {
			while (lex.isSkipChar(lex.getNextCharacter()) && lex.getCharacter()) {
			}
			if (!lex.isSkipChar(lex.getNextCharacter())) {
				throw new CompilerException("Syntax error: Characters found after #END DOCUMENT");
			}
		}
	}
	
	// call the variable define rule
	public void variableDefine() throws CompilerException {
		// This could be empty so do not throw an error if this is not true
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SDF")) {
			lex.getNextToken();
			// Go crazy and throw errors now if this is not what you expect
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("NAME")) {
				throw new CompilerException("A #NAME annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			
			lex.getNextToken();
			// get the text but without adding a node
			lex.getNextToken();
			
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("VAL")) {
				throw new CompilerException("A #VALUE annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			
			lex.getNextToken();
			// get the text but without adding a node
			lex.getNextToken();
			
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("EDF")) {
				throw new CompilerException("A #END DEFINE annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			new Node<Token>(new Token( Compiler.tokens.get(Compiler.tokens.size()-4).getMD(), "DEF", Compiler.tokens.get(Compiler.tokens.size()-2).getMD()), currentParent.peek());
			
			lex.getNextToken();
			variableDefine();
		}
		
	}
	
	// Create text node if the token is text
	private void text() throws CompilerException {
		if (Compiler.getLastToken().getID().equalsIgnoreCase("TEXT")) {
			new Node<Token>(Compiler.getLastToken(), currentParent.peek());
			lex.getNextToken();
		}
	}

	// Body rule
	public void body() throws CompilerException {
		// need to check if it it's inner text unfortunately because
		// body needs to be called after innerText but we don't want
		// infinite recursion
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SU") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("TEXT") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("NL") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("SA") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("SV")) {
			innerText();
			body();
		}
		
		// paragraph?
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SP")) {
			paragraph();
			body();
		}
		// bold?
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SB")) {
			bold();
			body();
		}
		// italics?
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SI")) {
			italics();
			body();
		}
		// list?
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SL")) {
			list();
			body();
		}
	}

	// Could be text inside a rule
	private void innerText() throws CompilerException {
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SU")) {
			variableUse();
			innerText();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("TEXT")) {
			text();
			innerText();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("NL")) {
			newline();
			innerText();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SA")) {
			audio();
			innerText();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SV")) {
			video();
			innerText();
		}
	}

	// If this is a newline character, create a node
	private void newline() throws CompilerException {
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("NL")) {
			throw new CompilerException("A #NEWLINE annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		new Node<Token>(Compiler.getLastToken(), currentParent.peek());
		lex.getNextToken();
	}

	// If this is a use variable token, create a node
	public void variableUse() throws CompilerException {
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SU")) {
			
			lex.getNextToken();
			// get the text but without adding a node
			lex.getNextToken();
			
			// Make sure this ends with a #END USE
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("EU")) {
				throw new CompilerException("A #END USE annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			new Node<Token>(new Token(Compiler.tokens.get(Compiler.tokens.size()-2).getMD(), "USE", Compiler.tokens.get(Compiler.tokens.size()-2).getMD()), currentParent.peek());

			lex.getNextToken();	
		}
	}

	// If this is a head token, create a node and call sub rules
	@Override
	public void head() throws CompilerException {
		// This could be empty so do not throw an error if this is not true
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SH")) {
			currentParent.push(new Node<Token>(Compiler.getLastToken(), currentParent.peek()));
			lex.getNextToken();
			
			title();
			
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("EH")) {
				throw new CompilerException("A #END HEAD annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			
			new Node<Token>(Compiler.getLastToken(), currentParent.peek());
			currentParent.pop();
			
			lex.getNextToken();
		}
	}

	// If this is a title token, create a node and get the inner text
	@Override
	public void title() throws CompilerException {
		// This could be empty so do not throw an error if this is not true
		if (Compiler.getLastToken().getID().equalsIgnoreCase("ST")) {
			
			currentParent.push(new Node<Token>(Compiler.getLastToken(), currentParent.peek()));

			lex.getNextToken();
			
			text();
			
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("ET")) {
				throw new CompilerException("A #END HEAD annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			new Node<Token>(Compiler.getLastToken(), currentParent.peek());
			currentParent.pop();
			
			lex.getNextToken();
		}
	}

	// If this is a paragraph node, create a node and get the inner contents
	@Override
	public void paragraph() throws CompilerException {
		// This could be empty so do not throw an error if this is not true
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SP")) {
			currentParent.push(new Node<Token>(Compiler.getLastToken(), currentParent.peek()));
			lex.getNextToken();
			variableDefine();
			innerParagraph();
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("EP")) {
				throw new CompilerException("A #END PARAGRAPH annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			
			new Node<Token>(Compiler.getLastToken(), currentParent.peek());
			currentParent.pop();
			lex.getNextToken();
		}
	}

	// get the inner contents if there is any
	private void innerParagraph() throws CompilerException {
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SU") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("TEXT") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("NL") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("SA") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("SV")) {
			innerText();
			innerParagraph();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SB")) {
			bold();
			innerParagraph();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SI")) {
			italics();
			innerParagraph();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SL")) {
			list();
			innerParagraph();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("NL")) {
			newline();
			innerParagraph();
		}
	}

	// if this is a bold token, create a node and get the inner contents
	@Override
	public void bold() throws CompilerException {
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("SB")) {
			throw new CompilerException("A #START BOLD annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		currentParent.push(new Node<Token>(Compiler.getLastToken(), currentParent.peek()));
		lex.getNextToken();
		variableDefine();
		innerText();
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("EB")) {
			throw new CompilerException("A #END BOLD annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		
		new Node<Token>(Compiler.getLastToken(), currentParent.peek());
		currentParent.pop();
		lex.getNextToken();
	}

	// if this is an italic token, create a node and get the inner contents
	@Override
	public void italics() throws CompilerException {
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("SI")) {
			throw new CompilerException("A #START ITALICS annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		currentParent.push(new Node<Token>(Compiler.getLastToken(), currentParent.peek()));
		lex.getNextToken();
		variableDefine();
		innerText();
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("EI")) {
			throw new CompilerException("A #END ITALICS annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		
		new Node<Token>(Compiler.getLastToken(), currentParent.peek());
		currentParent.pop();
		lex.getNextToken();
	}

	// If this is a list, create a node, get the first item and other contents
	@Override
	public void list() throws CompilerException {
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("SL")) {
			throw new CompilerException("A #START LIST annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		currentParent.push(new Node<Token>(Compiler.getLastToken(), currentParent.peek()));
		lex.getNextToken();
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("SIT")) {
			throw new CompilerException("A #START ITEM annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		currentParent.push(new Node<Token>(Compiler.getLastToken(), currentParent.peek()));
		lex.getNextToken();
		variableDefine();
		innerList();
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("EIT")) {
			throw new CompilerException("A #END ITEM annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		
		new Node<Token>(Compiler.getLastToken(), currentParent.peek());
		currentParent.pop();
		lex.getNextToken();
		listItems();
		if (!Compiler.getLastToken().getID().equalsIgnoreCase("EL")) {
			throw new CompilerException("A #END LIST annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
		}
		
		new Node<Token>(Compiler.getLastToken(), currentParent.peek());
		currentParent.pop();
		lex.getNextToken();
	}

	// if this is an item node, create a node and get the tokens
	public void listItems() throws CompilerException {
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SIT")) {
			currentParent.push(new Node<Token>(Compiler.getLastToken(), currentParent.peek()));
			lex.getNextToken();
			variableDefine();
			innerList();
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("EIT")) {
				throw new CompilerException("A #END ITEM annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			
			new Node<Token>(Compiler.getLastToken(), currentParent.peek());
			currentParent.pop();
			lex.getNextToken();
			listItems();
		}
	}

	// get the inner contents
	private void innerList() throws CompilerException {
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SU") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("TEXT") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("NL") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("SA") ||
			Compiler.getLastToken().getID().equalsIgnoreCase("SV")) {
			innerText();
			innerList();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SB")) {
			bold();
			innerList();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SI")) {
			italics();
			innerList();
		}
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SL")) {
			list();
			innerList();
		}
	}

	// if this is an audio token, create a node and get the contents
	@Override
	public void audio() throws CompilerException {
		
		// This could be empty so do not throw an error if this is not true
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SA")) {
			lex.getNextToken();
			// we do not want a new text node
			lex.getNextToken();
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("EA")) {
				throw new CompilerException("A #END AUDIO annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			// create an audio token with audio html
			new Node<Token>(
					new Token(Compiler.tokens.get(Compiler.tokens.size()-3).getMD(),
							"AUDIO",
							"<audio controls><source src='" + Compiler.tokens.get(Compiler.tokens.size()-3).getMD() + "'></audio>"
							),
					currentParent.peek());
			lex.getNextToken();
		}
	}

	// if this is a video token, create a node and get the contents
	@Override
	public void video() throws CompilerException {
		
		// This could be empty so do not throw an error if this is not true
		if (Compiler.getLastToken().getID().equalsIgnoreCase("SV")) {
			lex.getNextToken();
			// we do not want a new text node so get the next token
			lex.getNextToken();
			if (!Compiler.getLastToken().getID().equalsIgnoreCase("EV")) {
				throw new CompilerException("A #END VIDEO annotation was expected when a " + Compiler.getLastToken().getMD() + " was found.");
			}
			// create a Video token with iframe html
			new Node<Token>(
					new Token(Compiler.tokens.get(Compiler.tokens.size()-3).getMD(),
							"VIDEO",
							"<iframe src='" + Compiler.tokens.get(Compiler.tokens.size()-2).getMD() + "'/>"
							),
					currentParent.peek());
			lex.getNextToken();
		}
	}

}
